using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace parts.tests
{
    [TestFixture]
    public class FunctionalUnitTests
    {
        [Test]
        public void ToDictionary_without_implementation() {
            var toDictionary = new FunctionalUnit("ToDictionary");
            toDictionary.AddInputPin(typeof(string));
            toDictionary.AddOutputPin(typeof(IDictionary<string, string>));

            var split_into_pairs = new FunctionalUnit("split_into_pairs");
            split_into_pairs.AddInputPin(typeof(string));
            split_into_pairs.AddOutputPin(typeof(IEnumerable<string>));

            var split_into_kvpairs = new FunctionalUnit("split_into_kvpairs");
            split_into_kvpairs.AddInputPin(typeof(IEnumerable<string>));
            split_into_kvpairs.AddOutputPin(typeof(IEnumerable<KeyValuePair<string, string>>));

            var build_dictionary = new FunctionalUnit("build_dictionary");
            build_dictionary.AddInputPin(typeof(IEnumerable<KeyValuePair<string, string>>));
            build_dictionary.AddOutputPin(typeof(IDictionary<string, string>));

            toDictionary.AddFunctionalUnit(split_into_pairs);
            toDictionary.AddFunctionalUnit(split_into_kvpairs);
            toDictionary.AddFunctionalUnit(build_dictionary);

            toDictionary.InputPin<string>().Add(split_into_pairs.InputPin<string>());
            split_into_pairs.OutputPin<IEnumerable<string>>().Add(split_into_kvpairs.InputPin<IEnumerable<string>>());
            split_into_kvpairs.OutputPin<IEnumerable<KeyValuePair<string, string>>>().Add(build_dictionary.InputPin<IEnumerable<KeyValuePair<string, string>>>());
            build_dictionary.OutputPin<IDictionary<string, string>>().Add(toDictionary.OutputPin<IDictionary<string, string>>());

            var allFunctionalUnits = new List<FunctionalUnit>();
            allFunctionalUnits.Add(toDictionary);
            allFunctionalUnits.Add(split_into_pairs);
            allFunctionalUnits.Add(split_into_kvpairs);
            allFunctionalUnits.Add(build_dictionary);

            Plot(allFunctionalUnits);

            var container = new Container();
            container.Add<string, IEnumerable<string>>(ToDictionaryMethods.SplitIntoPairs);

            var resolver = new Resolver(allFunctionalUnits, container);
        }

        private void Plot(List<FunctionalUnit> allFunctionalUnits) {
            foreach (var functionalUnit in allFunctionalUnits) {
                Console.WriteLine(functionalUnit.Name);
                foreach (var inputPin in functionalUnit.InputPins) {
                    Console.WriteLine("  {0}<{1}>", inputPin.Name, inputPin.Type);
                    foreach (var pin in inputPin.ConnectedPins) {
                        Console.WriteLine("    {0}", pin.OwningUnit.Name);
                    }
                }
                foreach (var outputPin in functionalUnit.OutputPins) {
                    Console.WriteLine("  {0}<{1}>", outputPin.Name, outputPin.Type);
                    foreach (var pin in outputPin.ConnectedPins) {
                        Console.WriteLine("    {0}", pin.OwningUnit.Name);
                    }
                }
            }

            foreach (var functionalUnit in allFunctionalUnits) {
                if (functionalUnit.ContainedUnits.Count() > 0) {
                    Console.WriteLine(functionalUnit.Name);
                    foreach (var containedUnit in functionalUnit.ContainedUnits) {
                        Console.WriteLine("  {0}", containedUnit.Name);
                    }
                }
            }
        }
    }

    public class Container
    {
        private readonly IDictionary<string, object> implementations = new Dictionary<string, object>();

        public void Add<TInput, TOutput>(Func<TInput, TOutput> func) {
            implementations.Add(func.Method.Name, func);    
        }

        public object Resolve(string name, IEnumerable<Pin> inputPins, IEnumerable<Pin> outputPins) {
            var implementation = implementations[name];
            return implementation;
        }
    }

    public class Resolver
    {
        public Resolver(IEnumerable<FunctionalUnit> model, Container container) {
            foreach (var functionalUnit in model) {
                if (functionalUnit.ContainedUnits.Count() > 0) {
                    break;
                }
                var implementation = container.Resolve(functionalUnit.Name, functionalUnit.InputPins, functionalUnit.OutputPins);
                functionalUnit.Implementation = implementation;
            }

            foreach (var functionalUnit in model) {
                if (functionalUnit.Implementation != null) {
                    foreach (var outputPin in functionalUnit.OutputPins) {
                        foreach (var connectedPin in outputPin.ConnectedPins) {
                            var inputPin = connectedPin.OwningUnit.InputPins.First();
                            
                        }
                    }
                }
            }
        }
    }

    public class FunctionalUnit
    {
        private readonly string name;
        private readonly IList<Pin> inputPins = new List<Pin>();
        private readonly IList<Pin> outputPins = new List<Pin>();
        private readonly IList<FunctionalUnit> containedUnits = new List<FunctionalUnit>();

        public FunctionalUnit(string name) {
            this.name = name;
        }

        public string Name {
            get { return name; }
        }

        public void AddInputPin(Type type) {
            inputPins.Add(new Pin(this, "Process", type));
        }

        public void AddOutputPin(Type type) {
            outputPins.Add(new Pin(this, "Result", type));
        }

        public Pin InputPin<T>() {
            return inputPins.FirstOrDefault(x => x.Type == typeof(T));
        }

        public Pin OutputPin<T>() {
            return outputPins.FirstOrDefault(x => x.Type == typeof(T));
        }

        public IEnumerable<Pin> InputPins {
            get { return inputPins; }
        }

        public IEnumerable<Pin> OutputPins {
            get { return outputPins; }
        }

        public void AddFunctionalUnit(FunctionalUnit containedUnit) {
            containedUnits.Add(containedUnit);
        }

        public IEnumerable<FunctionalUnit> ContainedUnits {
            get { return containedUnits; }
        }

        public object Implementation { get; set; }  // Das sollte eher in eine Struktur, die parallel zum Modell aufgebaut wird
    }

    public class Pin
    {
        private readonly FunctionalUnit owningUnit;
        private readonly string name;
        private readonly Type type;
        private readonly IList<Pin> connectedPins = new List<Pin>();

        public Pin(FunctionalUnit owningUnit, string name, Type type) {
            this.owningUnit = owningUnit;
            this.name = name;
            this.type = type;
        }

        public FunctionalUnit OwningUnit {
            get { return owningUnit; }
        }
        
        public string Name {
            get { return name; }
        }

        public Type Type {
            get { return type; }
        }

        public void Add(Pin pin) {
            connectedPins.Add(pin);
        }

        public IEnumerable<Pin> ConnectedPins {
            get { return connectedPins; }
        }
    }
}